/** @file    ButlerBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: ButlerBundle.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the wosh::services::ButlerBundle class.
 * The header for this class can be found in ButlerBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "ButlerBundle.h"

 #include "ButlerImpl.h"
 #include "ButlerBot.h"

 #include <core/ObjectAllocator.h>
 #include <core/Fact.h>
 #include <core/Table.h>

 #include <core/MessageProcessingCommon.h>
 #include <core/SystemError.h>
 #include <core/WoshKernel.h>

 #include <framework/communication/AudioMessage.h>


using namespace std;
using namespace wosh::communication;
namespace wosh {
 namespace services {

 WOSH_REGISTER(wosh::services::ButlerBundle, "wosh::interfaces::services::Butler", _Butler_VERSION, _static_ButlerBundle1 )
 WOSH_REGISTER(wosh::services::ButlerBundle, "wosh::BundleGeneric", _Butler_VERSION, _static_ButlerBundle2 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

ButlerBundle::ButlerBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _Butler_NAME, false );
	Properties.update( _KEY_Version, _Butler_VERSION );

//Log.setLevel(LOG_VERBOSE);

	this->objPermission.setOwner(_Butler_USER);
	this->objPermission.setGroup("root");

	Log(LOG_DEBUG, " Configuring workers.." );
	this->butlerWorker = new ButlerImpl(*this);
	this->butlerWorker->setListener(this);
	this->butlerWorker->setDoorBellNotify(1);
	this->butlerWorker->setDoorBellPriority( Notification::PRIORITY_DEFAULT );

	this->butlerBot = new ButlerBot(*this);
	//this->butlerBot->setListener(this);

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _CommunicatorService_KEY_ChannelType,	(int)Notification::TYPE_TEXTUAL, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_ChannelLink,	(int)Communicator::LINK_ASYNCH, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Cost,			0.0,Permission::RW_R__R__() );
	Properties.set( _CommunicatorService_KEY_Priority,		10,	Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Reliability,	10,	Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Protocol,		(int)Notification::PROTOCOL_SHELL + Notification::PROTOCOL_HUMAN,	Permission::RW_R__R__() );

	Properties.set( _Butler_KEY_DoorBellNotify,				1,	Permission::RW_R__R__() );
	Properties.set( _Butler_KEY_DoorBellPriority,			(int)Notification::PRIORITY_DEFAULT,	Permission::RW_R__R__() );
	Properties.monitor(_Butler_KEY_DoorBellNotify);
	Properties.monitor(_Butler_KEY_DoorBellPriority);

	Log(LOG_DEBUG, " Registering methods.." );
	MethodMessageResponse* mmSendMessageToUser = Methods.create<MethodMessageResponse>( _CommunicatorService_METHOD_sendmessage, "send message to (wosh) username" );
	mmSendMessageToUser->setMethod( this, (MethodMessageResponsePtr)&ButlerBundle::mmDoSendMessageToUser );
	mmSendMessageToUser->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodRequest* mmListDoorBells = Methods.create<MethodRequest>( _METHOD_List, "List DoorBells" );
	mmListDoorBells->setMethod( this, (MethodRequestPtr)&ButlerBundle::mmDoListDoorBells );
	mmListDoorBells->getPermission().setMask( Permission::RX, Permission::RX, Permission::RX );

	MethodWRESULT* mmUpdateDoorBells = Methods.create<MethodWRESULT>( _BigBrother_METHOD_update, "force update DoorBells" );
	mmUpdateDoorBells->setMethod( this, (MethodWRESULTPtr)&ButlerBundle::updateDoorBells );
	mmUpdateDoorBells->getPermission().setMask( Permission::RX, Permission::Read, Permission::Read );

	setBundleState(Bundle::STATE_CREATED, false);
 }


ButlerBundle::~ButlerBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~ButlerBundle() : Destroying while Bundle is running! Trying to stop.." );
		bundleStop();
	 }
	delete this->butlerWorker; this->butlerWorker = NULL;
	delete this->butlerBot; this->butlerBot = NULL;
	Log(LOG_VERBOSE, ":~ButlerBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT ButlerBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	User* butlerUser = new User(66, _Butler_USER, "", _Butler_USER ";root;virtual" );
	butlerUser->getProperties().set( "CommunicatorForce", this->getURI().toString() );
	butlerUser->getProperties().set( "ProtocolForce", "PROTOCOL_HUMAN" );
	WoshKernel::getInstance()->users().registerUser(butlerUser);

	BusSecurity.setMessageHandler(this);
	WRESULT bus_s_connected = BusSecurity.connect( _Bus_Security );
	if ( WFAILED(bus_s_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Security")..", bus_s_connected );

	BusBuilding.setMessageHandler(this);
	WRESULT bus_b_connected = BusBuilding.connect( _Bus_Building, true ); // create bus if missing
	if ( WFAILED(bus_b_connected) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus (wosh.Bus.Building)..", bus_b_connected );

	WRESULT ret = WRET_OK;
	ret += this->butlerBot->startThread( 15000 );
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED waiting RUNNING state of butlerBot.." );
		return WRET_ERR_INTERNAL;
	 }
	ret += BundleGeneric::start_SynchThread( this->butlerWorker );
	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event()

	Response* response = new Response( _METHOD_ListProperties, Properties.toTable(false) );
	Message* message = new Message(response);
	message->setSource(this);
	message->getDestination().fromString("*:Communication");
	message->setSecurityToken( generateCredentials() );
	BusCore.postMessage(message);

	Message* msg_event = new Message( new Fact( _CommunicatorService_EVENT_UserOnline, _Butler_USER ) );
	msg_event->setSource(this);
	msg_event->setDestinationBroadcast(true);
	msg_event->setSecurityToken( generateCredentials() );
	BusSecurity.postMessage( msg_event );

	return ret;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ButlerBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

//	WoshKernel::getInstance()->users().unRegisterUser(_Butler_USER);

	Message* msg_event = new Message( new Fact( _CommunicatorService_EVENT_UserOffline, _Butler_USER ) );
	msg_event->setSource(this);
	msg_event->setDestinationBroadcast(true);
	msg_event->setSecurityToken( generateCredentials() );
	BusSecurity.postMessage( msg_event );

	WRESULT bus_b_disconnected = BusBuilding.disconnect();
	if ( WFAILED(bus_b_disconnected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStart", "FAILED#%d Disconnecting Bus (wosh.Bus.Building)..", bus_b_disconnected );

	WRESULT bus_s_disconnected = BusSecurity.disconnect();
	if ( WFAILED(bus_s_disconnected) )
		SystemError::raise(this, Log, SystemError::WARNING, 0, "bundleStart", "FAILED#%d Disconnecting Bus ("_Bus_Security")..", bus_s_disconnected );

	WRESULT ret = WRET_OK;
	ret += this->butlerBot->quitThread( 15000 );
	if ( WFAILED(ret) ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStop", "FAILED waiting STOPPED state of butlerBot.." );
		return WRET_ERR_INTERNAL;
	 }
	ret = BundleGeneric::stop_SynchThread( this->butlerWorker );
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event()

	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void ButlerBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	// avoid double-response

	if ( message.getSourceBus() == BusCore.getBusName() ) {
		if ( message.getCurrentBus() == BusSecurity.getBusName() )
			return;
	 }
	else if ( message.getSourceBus() == BusSecurity.getBusName() ) {
		if ( message.getCurrentBus() == BusCore.getBusName() )
			return;
	 }

	this->butlerWorker->evalMessageIncoming( message );

	if ( !MessageFilter::isMessageTarget(message, this) )
		return;

	// request/update building objects/devices
	// retrieve info
	if ( message.getContent()->isResponse() ) {
		this->butlerWorker->evalResponse(message);
		return;
	 }

	if ( message.getContent()->isNotification() ) {
		if ( message.getDestination().isBroadcast() )
			return;
		const Notification* notif = message.getContent()->asNotification();
		WRESULT sent = notify( notif );
		Message* reply = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_sent, new Variant(notif->getID()), sent ) );
		BusCore.postMessage(reply);
		return;
	 }

	BundleGeneric::busMessage(message, source);
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS


//////////////////////////////////////////////////////////////////////////////////////////// GENERATED EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT

bool ButlerBundle::updatingProperty( bool& do_update, const Data& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	if ( property_current.getKey() == _Butler_KEY_DoorBellNotify ) {
		this->butlerWorker->setDoorBellNotify( value_proposed.toInteger() );
		return true;
	 }
	else if ( property_current.getKey() == _Butler_KEY_DoorBellPriority ) {
		this->butlerWorker->setDoorBellPriority( (Notification::PRIORITY)value_proposed.toInteger() );
		return true;
	 }
	else // forward processing to base class
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

////////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENT
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ButlerBundle::updateObject( MessageContent* content, const string& uri, ButlerImpl* )
 {
	Message* message = new Message( content );
	message->setSource(this);
	message->getDestination().fromString(uri);
	message->setDestinationBus( _Bus_Core );
	message->setSecurityToken( generateCredentials() );
	BusCore.postMessage(message);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

bool ButlerBundle::isUserReachable( const string& wosh_username )
 {
	return ( wosh_username == _Butler_USER && isBundleRunning() );
 }

WRESULT ButlerBundle::sendMessageToUser( const string& wosh_username, const string& message )
 {
	(void)wosh_username; (void)message;
	return WRET_ERR_INTERNAL;
 }

WRESULT ButlerBundle::sendMessageTo( const string& custom_username, const string& message )
 {
	(void)custom_username; (void)message;
	return WRET_ERR_INTERNAL;
 }

WRESULT ButlerBundle::notify( const Notification* notification )
 {
	if ( notification == NULL ) return WRET_ERR_PARAM;
	if ( notification->getNotificationType() != Notification::TYPE_TEXTUAL ) {
		return WRET_ERR_ILLEGAL_USE;
	 }
	if ( notification->getRecipent_User() != _Butler_USER ) {
		return WRET_ERR_PARAM;
	 }

	return this->butlerBot->evaluateNotification(notification);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* ButlerBundle::mmDoSendMessageToUser( const Message* requestMessage )
 {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( !request->hasData() ) {
		if ( request->doOmitResponse() )
			return NULL;
		return new Response( request->getMethod(), WRET_ERR_PARAM, "Invalid data" );
	 }

	std::string username = "";
	std::string message = "";
	if ( request->getData()->isKindOf<List>() ) {
		const DataList* dl = dynamic_cast<const DataList*>(request->getData());
		if ( dl->size() < 2 ) {
			if ( request->doOmitResponse() )
				return NULL;
			return new Response( request->getMethod(), WRET_ERR_PARAM, "Invalid arguments [username, message]" );
		 }
		username = dl->get(0).getData();
		dl->join(message, " ", 1);
	 }

	std::string sender = "";
	if ( requestMessage->getSecurityToken() != NULL )
		sender = requestMessage->getSecurityToken()->getUser();

	Log(LOG_INFO, "mmDoSendMessageToUser(%s) : %d chars", username.c_str(), message.size() );

	if ( sender != username && sender != "wosh ")
		message += " [by " + sender + "]";

	WRESULT ret = sendMessageToUser( username, message );

	if ( request->doOmitResponse() )
		return NULL;
	if ( WSUCCEEDED(ret) )
		return new Response( request->getMethod(), ret, "Sent!" );
	return new Response( request->getMethod(), ret, "Internal Error!" );
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* ButlerBundle::mmDoListDoorBells( const Request* request )
 {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().getData();
	Log(LOG_VERBOSE, ":mmDoListDoorBells(%s)", option.c_str() );

	if ( option == "s" || option == "short" ) {
		DataList* objs =  new DataList();
		this->butlerWorker->getDoorBells().mutex().lockForRead();
		std::map<string, DoorBellInfo*>::const_iterator it;
		std::map<string, DoorBellInfo*>::const_iterator it_end = this->butlerWorker->getDoorBells().stdEnd();
		for ( it=this->butlerWorker->getDoorBells().stdBegin(); it!=it_end; it++ ) {
			objs->add( new Data(it->first) );
		 }
		this->butlerWorker->getDoorBells().mutex().unLock();
		return new Response( request->getMethod(), objs );
	 }
	else {
		this->butlerWorker->getDoorBells().mutex().lockForRead();
		Table* tbl = new Table( this->butlerWorker->getDoorBells().size_(), 3, true );
		tbl->setTableName("Door Bells");
		tbl->setHeaderHorizontal( new Data("Name"), 0);
		tbl->setHeaderHorizontal( new Data("Owner"), 1);
		tbl->setHeaderHorizontal( new Data("LastEvent"), 2);
		unsigned int iRow = 0;
		std::map<string, DoorBellInfo*>::const_iterator it;
		std::map<string, DoorBellInfo*>::const_iterator it_end = this->butlerWorker->getDoorBells().stdEnd();
		for ( it=this->butlerWorker->getDoorBells().stdBegin(); it!=it_end; it++ ) {
			if ( it->second == NULL ) continue;
			tbl->set( new Data(it->second->uri.toString()), iRow, 0);
			tbl->set( new Data(it->second->owner), iRow, 1);
			tbl->set( new Data(it->second->getLastEvent()), iRow, 2);
			++iRow;
		 }
		this->butlerWorker->getDoorBells().mutex().unLock();
		return new Response( request->getMethod(), tbl );
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT ButlerBundle::updateDoorBells() {
	this->butlerWorker->updateDoorBells();
	return WRET_OK;
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace services
}; // namespace wosh
